using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Xml.Serialization;

namespace ColladaDotNet
{
	public partial class COLLADA : ColladaXmlNode
	{
		public override IList<ColladaXmlNode> ChildNodes
		{
			get
			{
				var list = new List<ColladaXmlNode>();
				if (Items != null)
					foreach (object e in Items) if (e is ColladaXmlNode) list.Add((ColladaXmlNode)e);
				if (extra != null)
					foreach (object e in extra) if (e is ColladaXmlNode) list.Add((ColladaXmlNode)e);
				if (asset != null)
					list.Add(asset);
				if (scene != null)
					list.Add(scene);
				return list;
			}
		}

		private static XmlSerializer serializer;
		public static XmlSerializer Serializer
		{
			get
			{
				if (serializer == null)
				{
					serializer = new XmlSerializer(typeof(COLLADA));
				}
				return serializer;
			}
			set
			{
				serializer = value;
			}
		}

		public static COLLADA Load(Stream stream)
		{
			return (COLLADA)Serializer.Deserialize(stream);
		}
		public static COLLADA Load(string fileName)
		{
			//var p = Directory.GetCurrentDirectory();
			//var s = Path.GetDirectoryName(fileName);
			//if (!string.IsNullOrEmpty(s))
			//    Directory.SetCurrentDirectory(s);

			using (var stream = File.OpenRead(fileName))
			{
				var r= Load(stream);
				//Directory.SetCurrentDirectory(p);
				return r;
			}
		}
		public T GetItem<T>() where T: ColladaXmlNode
		{
					foreach (var xmlNode in itemsField)
					{
						if (xmlNode is T)
							return (T)xmlNode;
					}
				return null;
		}
		public void ReplaceItem<T>(T value) where T : ColladaXmlNode
		{
			if (itemsField == null || itemsField.Length == 0)
			{
				itemsField = new ColladaXmlNode[]{value};
				return;
			}
			for (int i = 0; i < itemsField.Length; i++)
			{
				var xmlNode = itemsField[i];
				if (xmlNode is T)
				{
					itemsField[i] = value;
				}
			}
			itemsField = AddToArray(itemsField, value);
		}

		[XmlIgnore]
		public library_effects library_effects
		{
			get
			{
				return GetItem<library_effects>();
			}
			set
			{
				ReplaceItem<library_effects>(value);
			}
		}

		[XmlIgnore]
		public library_geometries library_geometries
		{
			get
			{
				return GetItem<library_geometries>();
			}
			set
			{
				ReplaceItem<library_geometries>(value);
			}
		}
		[XmlIgnore]
		public library_materials library_materials
		{
			get
			{
				return GetItem<library_materials>();
			}
			set
			{
				ReplaceItem<library_materials>(value);
			}
		}
		[XmlIgnore]
		public library_images library_images
		{
			get
			{
				return GetItem<library_images>();
			}
			set
			{
				ReplaceItem(value);
			}

		}
		[XmlIgnore]
		public library_animation_clips library_animation_clips
		{
			get
			{
				return GetItem<library_animation_clips>();
			}
			set
			{
				ReplaceItem(value);
			}

		}
		[XmlIgnore]
		public library_animations library_animations
		{
			get
			{
				return GetItem<library_animations>();
			}
			set
			{
				ReplaceItem(value);
			}

		}
		[XmlIgnore]
		public library_cameras library_cameras
		{
			get
			{
				return GetItem<library_cameras>();
			}
			set
			{
				ReplaceItem(value);
			}

		}
		[XmlIgnore]
		public library_controllers library_controllers
		{
			get
			{
				return GetItem<library_controllers>();
			}
			set
			{
				ReplaceItem(value);
			}
		}

		[XmlIgnore]
		public library_lights library_lights
		{
			get
			{
				return GetItem<library_lights>();
			}
			set
			{
				ReplaceItem(value);
			}

		}
		
		[XmlIgnore]
		public library_physics_scenes library_physics_scenes
		{
			get
			{
				return GetItem<library_physics_scenes>();

			}
			set
			{
				ReplaceItem(value);
			}

		}
		[XmlIgnore]
		public library_visual_scenes library_visual_scenes
		{
			get
			{
				return GetItem<library_visual_scenes>();

			}
			set
			{
				ReplaceItem(value);
			}

		}
		public void Save(string fileName)
		{
			using (var stream = File.Create(fileName))
			{
				Save(stream);
			}
		}

		public void Save(Stream stream)
		{
			Serializer.Serialize(stream,this);
		}

		public static T[] AddToArray<T>(T[] effect, T e)
		{
			if (effect == null ||effect.Length == 0)
				return new T[]{e};
			T[] v = new T[effect.Length+1];
			effect.CopyTo(v,0);
			v[v.Length - 1] = e;
			return v;
		}
		private static void CopyPosition(node src, node dst, COLLADA dstFile)
		{
			dst.Matrix = src.Matrix;
			if (src.node1 != null)
				foreach (var e in src.node1)
				{
					var dse = dstFile.GetElementById(e.id);
					if (dse != null)
					{
						CopyPosition(e, (node)dse, dstFile);
					}
					else
					{
						dst.node1 = COLLADA.AddToArray<node>(dst.node1, e);
					}
				}
		}

		private static void CopyProperties<T>(T src, T dst, bool overwright)
		{
			if (!overwright)
				return;
			foreach (var info in typeof(T).GetProperties())
			{
				if (Attribute.GetCustomAttribute(info, typeof(XmlIgnoreAttribute)) == null)
				{
					info.SetValue(dst, info.GetValue(src, null), null);
				}
			}
		}
		public void CopyTo(COLLADA dstFile, bool overwright)
		{
			if (this.library_effects != null)
			{
				if (dstFile.library_effects == null)
					dstFile.library_effects = new library_effects();

				foreach (var e in this.library_effects.effect)
				{
					var dse = dstFile.GetElementById(e.id);
					if (dse != null)
						CopyProperties(e, dse, overwright);
					else
						dstFile.library_effects.effect = COLLADA.AddToArray(dstFile.library_effects.effect, e);
				}
			}
			CopyLibraryMaterials(dstFile, overwright);
			CopyLibraryGeometries(dstFile, overwright);
			CopyControllersLibrary(dstFile, overwright);
			CopyImagesLibrary(dstFile, overwright);
			CopyAnimationsLibrary(dstFile, overwright);
			CopyCamerasLibrary(dstFile, overwright);
			if (this.library_visual_scenes != null)
			{
				if (dstFile.library_visual_scenes == null)
					dstFile.library_visual_scenes = this.library_visual_scenes;
				else
				{
					var srcScenes = this.library_visual_scenes.visual_scene;
					var dstScenes = dstFile.library_visual_scenes.visual_scene;
					if (srcScenes.Length > 0)
					{
						if (dstScenes.Length == 0)
							dstFile.library_visual_scenes.visual_scene = srcScenes;
						else
						{
							CopySceneNodes(dstFile, overwright, srcScenes, dstScenes);
						}
					}
				}
			}
		}

		private void CopySceneNodes(COLLADA dstFile, bool overwright, visual_scene[] srcScenes, visual_scene[] dstScenes)
		{
			foreach (var node in srcScenes[0].node)
			{
				var dse = (node)dstFile.GetElementById(node.id);
				if (dse != null)
				{
					if (overwright)
						CopyProperties(node, dse,overwright);
					else
						CopyPosition(node, dse,dstFile);
				}
				else
					dstScenes[0].node = COLLADA.AddToArray<node>(dstScenes[0].node, node);
			}
		}

		private void CopyCamerasLibrary(COLLADA dstFile, bool overwright)
		{
			if (this.library_cameras != null)
			{
				if (dstFile.library_cameras == null)
					dstFile.library_cameras = new library_cameras();

				foreach (var e in this.library_cameras.camera)
				{
					var dse = dstFile.GetElementById(e.id);
					if (dse != null)
						CopyProperties(e, dse,overwright);
					else
						dstFile.library_cameras.camera = COLLADA.AddToArray(dstFile.library_cameras.camera, e);
				}
			}
		}

		private void CopyAnimationsLibrary(COLLADA dstFile, bool overwright)
		{
			if (this.library_animations != null)
			{
				if (dstFile.library_animations == null)
					dstFile.library_animations = new library_animations();

				foreach (var e in this.library_animations.animation)
				{
					var dse = dstFile.GetElementById(e.id);
					if (dse != null)
						CopyProperties(e, dse,overwright);
					else
						dstFile.library_animations.animation = COLLADA.AddToArray(dstFile.library_animations.animation, e);
				}
			}
		}

		private void CopyImagesLibrary(COLLADA dstFile, bool overwright)
		{
			if (this.library_images != null)
			{
				if (dstFile.library_images == null)
					dstFile.library_images = new library_images();

				foreach (var e in this.library_images.image)
				{
					var dse = dstFile.GetElementById(e.id);
					if (dse != null)
						CopyProperties(e, dse,overwright);
					else
						dstFile.library_images.image = COLLADA.AddToArray(dstFile.library_images.image, e);
				}
			}
		}

		private void CopyControllersLibrary(COLLADA dstFile, bool overwright)
		{
			if (this.library_controllers != null)
			{
				if (dstFile.library_controllers == null)
					dstFile.library_controllers = new library_controllers();

				foreach (var e in this.library_controllers.controller)
				{
					var dse = dstFile.GetElementById(e.id);
					if (dse != null)
						CopyProperties(e, dse,overwright);
					else
						dstFile.library_controllers.controller = COLLADA.AddToArray(dstFile.library_controllers.controller, e);
				}
			}
		}

		private void CopyLibraryGeometries(COLLADA dstFile, bool overwright)
		{
			if (this.library_geometries != null)
			{
				if (dstFile.library_geometries == null)
					dstFile.library_geometries = new library_geometries();

				foreach (var e in this.library_geometries.geometry)
				{
					var dse = dstFile.GetElementById(e.id);
					if (dse != null)
						CopyProperties(e, dse,overwright);
					else
						dstFile.library_geometries.geometry = COLLADA.AddToArray(dstFile.library_geometries.geometry, e);
				}
			}
		}

		private void CopyLibraryMaterials(COLLADA dstFile, bool overwright)
		{
			if (this.library_materials != null)
			{
				if (dstFile.library_materials == null)
					dstFile.library_materials = new library_materials();

				foreach (var e in this.library_materials.material)
				{
					var dse = dstFile.GetElementById(e.id);
					if (dse != null)
						CopyProperties(e, dse,overwright);
					else
						dstFile.library_materials.material = COLLADA.AddToArray(dstFile.library_materials.material, e);
				}
			}
		}
		public geometry CreateGeometry(string id, string name)
		{
			var node = new geometry();
			node.id = id;
			node.name = name;
			if (library_geometries == null)
			{
				library_geometries = new library_geometries();
			}
			library_geometries.Add(node);
			return node;
		}
	
		public static COLLADA CreateNewScene(string visualSceneName, string physicsSceneName)
		{
			COLLADA doc = CreateNew();
			doc.scene = new COLLADAScene() { instance_physics_scene = new InstanceWithExtra[] { new InstanceWithExtra() { url = "#" + physicsSceneName } }, instance_visual_scene = new InstanceWithExtra() { url = "#" + visualSceneName } };
			doc.library_visual_scenes.visual_scene = new visual_scene[] { new visual_scene() { id = visualSceneName, name = visualSceneName } };
			doc.library_physics_scenes.physics_scene = new physics_scene[] { new physics_scene() { id = physicsSceneName, name = physicsSceneName } };
			return doc;
		}

		private static COLLADA CreateNew()
		{
			var doc = new COLLADA();
			doc.asset = new asset()
			            	{
			            			created = DateTime.Now, 
									modified = DateTime.Now,
									unit = new assetUnit() { meter = 0.01, name = "centimeter" },
									//unit = new assetUnit() { meter = 0.0254, name = "inch" },
									up_axis = UpAxisType.Z_UP
			            	};
			doc.asset.contributor = new assetContributor[]{new assetContributor(){author = "ColladaDotNet",comments = "",copyright = "",authoring_tool = "ColladaDotNet by GLebedev",source_data = ""}};
			doc.library_materials = new library_materials();
			doc.library_animation_clips = new library_animation_clips();
			doc.library_animations = new library_animations();
			doc.library_cameras = new library_cameras();
			doc.library_controllers = new library_controllers();
			doc.library_effects = new library_effects();
			doc.library_geometries = new library_geometries();
			doc.library_images = new library_images();
			doc.library_lights = new library_lights();
			doc.library_materials = new library_materials();
			doc.library_visual_scenes = new library_visual_scenes();
			doc.library_physics_scenes = new library_physics_scenes();
			return doc;
		}

		public image CreateImage(string id, string path)
		{
			var node = new image();
			node.id = id;
			node.Item = path;
			if (library_images == null)
			{
				library_images = new library_images();
			}
			library_images.Add(node);
			return node;
		}
		public image CreateImage(string id, byte[] data)
		{
			var node = new image();
			node.id = id;
			node.Item = data;
			if (library_images == null)
			{
				library_images = new library_images();
			}
			library_images.Add(node);
			return node;
		}

		public effect CreateEffect(string id, image img)
		{
			var node = new effect();
			node.id = id;
			var common = new effectFx_profile_abstractProfile_COMMON();
			node.Items = new ColladaXmlNode[]{common};
			common.Items =
				new ColladaXmlNode[]
					{
						new common_newparam_type()
						    {
								ItemElementName = ItemChoiceType2.@surface,
								sid = id+"-surface", 
						        Item =new fx_surface_common()
						        {
                                    type = fx_surface_type_enum.Item2D,
                                    init_from = new []
                                	{
                            			new fx_surface_init_from_common(){Value = img.id}
                                	} 
						        }
						    },
						new common_newparam_type()
						{
			                sid = id+"-sampler", 
						    Item = new fx_sampler2D_common()
						        {
                                    source = id+"-surface"
						        }, ItemElementName = ItemChoiceType2.@sampler2D
						}
					};
			common.technique = 
				new effectFx_profile_abstractProfile_COMMONTechnique()
				{
					Item = new effectFx_profile_abstractProfile_COMMONTechniqueBlinn()
       						{
								emission = new common_color_or_texture_type()
						           	{
										Item = new common_color_or_texture_typeColor()
										{
											Text = new double[] { 0, 0, 0, 1 }
										}
						           	},
								ambient = new common_color_or_texture_type()
								{
									Item = new common_color_or_texture_typeTexture()
									{
										texture = id + "-sampler",
										texcoord = "CHANNEL1"
									}
								},
								diffuse = new common_color_or_texture_type()
								{
									Item = new common_color_or_texture_typeTexture()
									{
										texture = id + "-sampler",
										texcoord = "CHANNEL1"
									}
								}

							}
			    };
			if (library_effects == null)
			{
				library_effects = new library_effects();
			}
			library_effects.Add(node);
			return node;
		}

		public material CreateMaterial(string id, string name, effect eff)
		{
			var node = new material();
			node.id = id;
			node.name = name;
			node.instance_effect = new instance_effect(){url = "#"+eff.id};
			if (library_materials == null)
			{
				library_materials = new library_materials();
			}
			library_materials.Add(node);
			return node;
		}

		public effect CreateEffect(string id, Color color)
		{
			var node = new effect();
			node.id = id;
			var common = new effectFx_profile_abstractProfile_COMMON();
			node.Items = new ColladaXmlNode[] { common };
			common.Items =
				new ColladaXmlNode[]
					{
					};
			common.technique =
				new effectFx_profile_abstractProfile_COMMONTechnique()
				{
					Item = new effectFx_profile_abstractProfile_COMMONTechniqueBlinn()
					{
						emission = new common_color_or_texture_type()
						{
							Item = new common_color_or_texture_typeColor()
							{
								Text = new double[] { 0, 0, 0, 1 }
							}
						},
						ambient = new common_color_or_texture_type()
						{
							Item = new common_color_or_texture_typeColor()
							{
								Text = new double[] { color.R / 255.0, color.G / 255.0, color.B / 255.0, 1 }
							}
						},
						diffuse = new common_color_or_texture_type()
						{
							Item = new common_color_or_texture_typeColor()
							{
								Text = new double[] { color.R / 255.0, color.G / 255.0, color.B / 255.0, 1 }
							}
						}
					}
				};
			if (library_effects == null)
			{
				library_effects = new library_effects();
			}
			library_effects.Add(node);
			return node;
		}

		public controller CreateController(string id)
		{
			var node = new controller();
			node.id = id;
			if (library_controllers == null)
			{
				library_controllers = new library_controllers();
			}
			library_controllers.Add(node);
			return node;
		}
	}
}
